package service

import (
	sqlc "bgs/db/sqlc"
	"bgs/grpc/imp/constants"
	"bgs/grpc/imp/enums"
	"bgs/service/model"

	"bgs/grpc/gen/proxy2/inventory"
	"bgs/util"
	"context"

	"encoding/json"
	"fmt"
	"strconv"
	"time"
)

const (
	// CityCodeHangZhou 杭州城市码
	CityCodeHangZhou = 156330100
)

// CreateBrandWarehouseParam 创建本地仓库参数
type CreateBrandWarehouseParam struct {
	CreateParamsPtr *sqlc.CreateBrandWarehouseParams
}

// CreateBrandWarehouse 创建本地仓库(并同步到仓库侧)
func (s *WarehouseServiceImp) CreateBrandWarehouse(ctx context.Context, q *sqlc.Queries, p CreateBrandWarehouseParam) (brandWarehouse sqlc.BrandWarehouse, err error) {
	slog := util.NewContextLogger(ctx, s.slog)

	txTime := time.Now()
	syncAction := string(sqlc.EmSyncActionLCR)
	remoteWarehousePtr, err := s.addInventoryWarehouse(ctx, &model.WarehouseInfo{
		Name:    p.CreateParamsPtr.Name,
		Address: p.CreateParamsPtr.Address,
		Lat:     p.CreateParamsPtr.Lat,
		Lng:     p.CreateParamsPtr.Lng,
		Linkman: p.CreateParamsPtr.Linkman,
		Tel:     p.CreateParamsPtr.Tel,
	}, p.CreateParamsPtr.DeliveryModes, p.CreateParamsPtr.BrandID)

	if err != nil {
		return
	}
	slog.Info("1 仓库侧创建仓库 ok")
	remotePk := remoteWarehousePtr.ID

	// 此处忽略grpc传入的参数,使用添加到远程的默认值
	p.CreateParamsPtr.CityCode = strconv.FormatInt(int64(remoteWarehousePtr.CityCode), 10)
	p.CreateParamsPtr.RegionCode = remoteWarehousePtr.RegionCode
	p.CreateParamsPtr.Deadline = remoteWarehousePtr.Deadline
	p.CreateParamsPtr.StorageMode = int32(remoteWarehousePtr.StorageMode)
	p.CreateParamsPtr.StoreID = int64(remoteWarehousePtr.StoreID)
	p.CreateParamsPtr.PStoreID = int64(remoteWarehousePtr.PStoreID)
	p.CreateParamsPtr.PermissionID = int64(remoteWarehousePtr.PermissionID)
	p.CreateParamsPtr.RemotePk = remotePk

	brandWarehouse, err = q.CreateBrandWarehouse(ctx, *p.CreateParamsPtr)

	if err != nil {
		return
	}
	slog.Info("2 本地创建品牌仓库 ok")

	//2 设置同步
	localObj, localPkName, localFields := constants.SyncLocalObjBrandWarehouse, constants.SyncLocalPkNameBrandWarehouse, constants.SyncLocalFieldsBrandWarehouse
	remoteObj, remotePkName, remoteFields := constants.SyncRemoteObjBrandWarehouse, constants.SyncRemotePkNameBrandWarehouse, constants.SyncRemoteFieldsBrandWarehouse

	sync, err := q.UpsertSync(ctx, sqlc.UpsertSyncParams{
		LocalObj:         localObj,
		LocalPkName:      localPkName,
		LocalFields:      localFields,
		RemoteObj:        remoteObj,
		RemotePkName:     remotePkName,
		RemoteFields:     remoteFields,
		LatestSynced:     txTime,
		LatestSyncAction: syncAction,
	})
	if err != nil {
		return
	}
	slog.Info("3 设置仓库同步 ok")

	localPK := strconv.FormatInt(brandWarehouse.ID, 10)
	// 3 设置同步映射
	syncMapping, err := q.UpsertSyncMapping(ctx, sqlc.UpsertSyncMappingParams{
		SyncID:    sync.ID,
		LocalObj:  localObj,
		LocalPk:   localPK,
		RemoteObj: remoteObj,
		RemotePk:  strconv.FormatInt(remotePk, 10),
	})
	if err != nil {
		return
	}
	slog.Info("4 设置仓库同步映射 ok")

	// 4 设置同步日志
	// "remote_pk", "origin", "name", "tel", "linkman", "address", "lng", "lat", "city_code", "deadline", "storage_mode", "store_id", "p_store_id", "permission_id", "delivery_modes"
	syncData, err := json.Marshal(brandWarehouse)
	if err != nil {
		return
	}

	maxVer, err := q.GetSyncLogMaxVer(ctx, sqlc.NInt64FromInt64(syncMapping.ID))
	if err != nil {
		return
	}

	_, err = q.CreateSyncLog(ctx, sqlc.CreateSyncLogParams{
		SyncID:        sync.ID,
		SyncMappingID: sqlc.NInt64FromInt64(syncMapping.ID),
		LocalObj:      localObj,
		LocalPk:       localPK,
		RemoteObj:     remoteObj,
		RemotePk:      strconv.FormatInt(remotePk, 10),
		Synced:        txTime,
		Action:        syncAction,
		Data:          syncData,
		SyncStatus:    string(enums.SyncStatusDone),
		Ver:           int64(maxVer + 1),
	})
	if err != nil {
		return
	}
	slog.Info("5 设置仓库同步日志 ok")

	data, err := json.Marshal(remoteWarehousePtr)
	if err != nil {
		return
	}

	// 5 设置同步远程对象
	_, err = q.UpsertSyncRemote(ctx, sqlc.UpsertSyncRemoteParams{
		SyncID:    sync.ID,
		RemoteObj: remoteObj,
		RemotePk:  strconv.FormatInt(remotePk, 10),
		Data:      data,
	})

	if err != nil {
		return
	}
	slog.Info("6 设置仓库同步远程对象 ok")

	err = s.setInventoryWarehouseExpress(ctx, &brandWarehouse)
	if err != nil {
		return
	}
	slog.Info("7 仓库侧设置仓库快递平台 ok")

	s.cacher.SaveWarehouse(brandWarehouse)
	slog.Info("8 新建的仓库更新到缓存 ok")

	s.cacher.SaveSyncMapping(syncMapping)
	slog.Info("9 新建的仓库同步映射更新到缓存 ok")

	return
}

// UpdateBrandWarehouseParam 更新本地仓库参数
type UpdateBrandWarehouseParam struct {
	UpdateParamsPtr *sqlc.UpdateBrandWarehouseParams
}

// UpdateBrandWarehouse 更新本地仓库(并同步到仓库侧)
func (s *WarehouseServiceImp) UpdateBrandWarehouse(ctx context.Context, q *sqlc.Queries, p UpdateBrandWarehouseParam) (brandWarehouse sqlc.BrandWarehouse, err error) {
	slog := util.NewContextLogger(ctx, s.slog)
	txTime := time.Now()
	syncAction := string(sqlc.EmSyncActionLUR)

	brandWarehouse, err = q.UpdateBrandWarehouse(ctx, *p.UpdateParamsPtr)
	if err != nil {
		return
	}
	slog.Info("1 更新本地品牌仓库 ok")
	brandWarehouse, err = q.GetBrandWarehouse(ctx, brandWarehouse.ID)
	if err != nil {
		return
	}

	remoteWarehousePtr, err := s.editInventoryWarehouse(ctx, &brandWarehouse)
	if err != nil {
		return
	}
	slog.Info("2 仓库侧编辑仓库 ok")

	//2 设置同步
	localObj, localPkName, localFields := constants.SyncLocalObjBrandWarehouse, constants.SyncLocalPkNameBrandWarehouse, constants.SyncLocalFieldsBrandWarehouse
	remoteObj, remotePkName, remoteFields := constants.SyncRemoteObjBrandWarehouse, constants.SyncRemotePkNameBrandWarehouse, constants.SyncRemoteFieldsBrandWarehouse

	localPk := fmt.Sprintf("%d", brandWarehouse.ID)
	remotePk := strconv.FormatInt(brandWarehouse.RemotePk, 10)

	sync, err := q.UpsertSync(ctx, sqlc.UpsertSyncParams{
		LocalObj:         localObj,
		LocalPkName:      localPkName,
		LocalFields:      localFields,
		RemoteObj:        remoteObj,
		RemotePkName:     remotePkName,
		RemoteFields:     remoteFields,
		LatestSynced:     txTime,
		LatestSyncAction: syncAction,
	})
	if err != nil {
		return
	}
	slog.Info("3 设置仓库同步 ok")

	localPK := strconv.FormatInt(brandWarehouse.ID, 10)
	// 3 设置同步映射
	syncMapping, err := q.UpsertSyncMapping(ctx, sqlc.UpsertSyncMappingParams{
		SyncID:    sync.ID,
		LocalObj:  localObj,
		LocalPk:   localPK,
		RemoteObj: remoteObj,
		RemotePk:  remotePk,
	})
	if err != nil {
		return
	}
	slog.Info("4 设置仓库同步映射 ok")

	syncData, err := json.Marshal(brandWarehouse)
	if err != nil {
		return
	}

	maxVer, err := q.GetSyncLogMaxVer(ctx, sqlc.NInt64FromInt64(syncMapping.ID))
	if err != nil {
		return
	}
	_, err = q.CreateSyncLog(ctx, sqlc.CreateSyncLogParams{
		SyncID:        sync.ID,
		SyncMappingID: sqlc.NInt64FromInt64(syncMapping.ID),
		LocalObj:      localObj,
		LocalPk:       localPk,
		RemoteObj:     remoteObj,
		RemotePk:      remotePk,
		Synced:        txTime,
		Action:        syncAction,
		Data:          syncData,
		SyncStatus:    string(enums.SyncStatusDone),
		Ver:           int64(maxVer + 1),
	})
	if err != nil {
		return
	}
	slog.Info("5 she zhi仓库同步日志 ok")

	data, err := json.Marshal(remoteWarehousePtr)
	if err != nil {
		return
	}

	// 5 设置同步远程对象
	_, err = q.UpsertSyncRemote(ctx, sqlc.UpsertSyncRemoteParams{
		SyncID:    sync.ID,
		RemoteObj: remoteObj,
		RemotePk:  remotePk,
		Data:      data,
	})

	if err != nil {
		return
	}
	slog.Info("6 设置仓库同步远程对象 ok")

	// err = s.setInventoryWarehouseExpress(ctx, &brandWarehouse, p.Token)
	// if err != nil {
	// 	return
	// }
	// slog.Info("7 仓库侧设置仓库快递平台 ok")

	s.cacher.SaveWarehouse(brandWarehouse)
	slog.Info("8 修改的仓库更新到缓存 ok")

	s.cacher.SaveSyncMapping(syncMapping)
	slog.Info("9 修改的仓库同步映射更新到缓存 ok")

	return
}

// RemoteWarehouse 远程的仓库信息
type RemoteWarehouse struct {
	SgID            uint32  `json:"sg_id"`
	ID              int64   `json:"id"`
	Name            string  `json:"name"`
	StoreID         uint32  `json:"store_id"`
	Address         string  `json:"address"`
	PickUp          bool    `json:"pick_up"`
	CityExpress     bool    `json:"city_express"`
	OrdinaryExpress bool    `json:"ordinary_express"`
	Latitude        float64 `json:"latitude"`
	Longitude       float64 `json:"longitude"`
	CityCode        int32   `json:"city_code"`
	RegionCode      string  `json:"region_code"`
	Contact         string  `json:"contact"`
	Mobile          string  `json:"mobile"`
	Deadline        string  `json:"deadline"`
	StorageMode     uint32  `json:"storage_mode"`
	PStoreID        uint32  `json:"p_store_id"`
	PermissionID    uint32  `json:"permission_id"`
	Source          string  `json:"source"`
}

// addInventoryWarehouse 添加仓库侧仓库
func (s *WarehouseServiceImp) addInventoryWarehouse(ctx context.Context, addressInfoPtr *model.WarehouseInfo, deliveryModes []string, brandID int64) (remoteWarehousePtr *RemoteWarehouse, err error) {
	slog := util.NewContextLogger(ctx, s.slog)

	lat, err := strconv.ParseFloat(addressInfoPtr.Lat, 32)
	if err != nil {
		err = fmt.Errorf("纬度(Latitude)转换失败:%s", err.Error())
		return
	}
	lng, err := strconv.ParseFloat(addressInfoPtr.Lng, 32)
	if err != nil {
		err = fmt.Errorf("经度(Longitude)转换失败:%s", err.Error())
		return
	}

	webTokenPtr, err := s.genInventoryToken(ctx, brandID)
	if err != nil {
		return
	}
	req := &inventory.AddWarehouseRequest{
		WebToken:        webTokenPtr,
		Id:              0,
		Name:            addressInfoPtr.Name,
		StoreId:         0,
		Address:         addressInfoPtr.Address,
		PickUp:          util.StringContains(deliveryModes, string(sqlc.EmDeliveryModeSelfPickup)),
		CityExpress:     util.StringContains(deliveryModes, string(sqlc.EmDeliveryModeInCity)),
		OrdinaryExpress: util.StringContains(deliveryModes, string(sqlc.EmDeliveryModeExpress)),
		Latitude:        lat,
		Longitude:       lng,
		CityCode:        CityCodeHangZhou,
		RegionCode:      "0571",
		Contact:         addressInfoPtr.Linkman,
		Mobile:          addressInfoPtr.Tel,
		Deadline:        "23:59:00",
		StorageMode:     0,
		PStoreId:        0,
		PermissionId:    0,
		Source:          string(enums.APPIDBGS),
	}

	slog.Infof("AddWarehouse req:%v", req)

	ctxWithTimout, cancel := s.proxy.NewContextWith(ctx)
	defer cancel()

	reply, err := s.proxy.InventoryCl.AddWarehouse(ctxWithTimout, req)
	if err != nil {
		errReply := s.proxy.GetInventoryErrorInfo(err)
		if errReply != nil {
			err = fmt.Errorf("仓库侧创建仓库错误:%s", errReply.ErrorMessage)
			slog.Infof("InventoryCl.AddWarehouse err====>type:%s,code:%s,msg:%s", errReply.ErrorType, errReply.ErrorCode, errReply.ErrorMessage)
		}
		return
	}
	remoteWarehousePtr = &RemoteWarehouse{
		SgID:            req.WebToken.SgId,
		ID:              int64(reply.Id),
		Name:            req.Name,
		StoreID:         req.StoreId,
		Address:         req.Address,
		PickUp:          req.PickUp,
		CityExpress:     req.CityExpress,
		OrdinaryExpress: req.OrdinaryExpress,
		Latitude:        req.Latitude,
		Longitude:       req.Longitude,
		CityCode:        req.CityCode,
		RegionCode:      req.RegionCode,
		Contact:         req.Contact,
		Mobile:          req.Mobile,
		Deadline:        req.Deadline,
		StorageMode:     req.StorageMode,
		PStoreID:        req.PStoreId,
		PermissionID:    req.PermissionId,
		Source:          req.Source,
	}
	return
}

// editInventoryWarehouse 修改仓库侧仓库
func (s *WarehouseServiceImp) editInventoryWarehouse(ctx context.Context, brandWarehousePtr *sqlc.BrandWarehouse) (remoteWarehousePtr *RemoteWarehouse, err error) {
	slog := util.NewContextLogger(ctx, s.slog)

	lat, err := strconv.ParseFloat(brandWarehousePtr.Lat, 32)
	if err != nil {
		err = fmt.Errorf("纬度(Latitude)转换失败:%s", err.Error())
		return
	}
	lng, err := strconv.ParseFloat(brandWarehousePtr.Lng, 32)
	if err != nil {
		err = fmt.Errorf("经度(Longitude)转换失败:%s", err.Error())
		return
	}
	cityCode, err := strconv.ParseInt(brandWarehousePtr.CityCode, 10, 32)
	webTokenPtr, err := s.genInventoryToken(ctx, brandWarehousePtr.BrandID)
	if err != nil {
		return
	}
	req := &inventory.EditWarehouseRequest{
		WebToken:        webTokenPtr,
		Id:              uint32(brandWarehousePtr.RemotePk),
		Name:            brandWarehousePtr.Name,
		StoreId:         uint32(brandWarehousePtr.StoreID),
		Address:         brandWarehousePtr.Address,
		PickUp:          util.StringContains(brandWarehousePtr.DeliveryModes, string(sqlc.EmDeliveryModeSelfPickup)),
		CityExpress:     util.StringContains(brandWarehousePtr.DeliveryModes, string(sqlc.EmDeliveryModeInCity)),
		OrdinaryExpress: util.StringContains(brandWarehousePtr.DeliveryModes, string(sqlc.EmDeliveryModeExpress)),
		Latitude:        lat,
		Longitude:       lng,
		CityCode:        int32(cityCode),
		RegionCode:      brandWarehousePtr.RegionCode,
		Contact:         brandWarehousePtr.Linkman,
		Mobile:          brandWarehousePtr.Tel,
		Deadline:        brandWarehousePtr.Deadline,
		StorageMode:     uint32(brandWarehousePtr.StorageMode),
		PStoreId:        uint32(brandWarehousePtr.PStoreID),
		Source:          string(enums.APPIDBGS),
	}

	slog.Infof("EditWarehouseRequest:%+v", req)

	ctxWithTimout, cancel := s.proxy.NewContextWith(ctx)
	defer cancel()

	_, err = s.proxy.InventoryCl.EditWarehouse(ctxWithTimout, req)
	if err != nil {
		errReply := s.proxy.GetInventoryErrorInfo(err)
		if errReply != nil {
			err = fmt.Errorf("仓库侧编辑仓库错误:%s", errReply.ErrorMessage)
			slog.Infof("InventoryCl.EditWarehouse err====>type:%s,code:%s,msg:%s", errReply.ErrorType, errReply.ErrorCode, errReply.ErrorMessage)
		}
		return
	}

	remoteWarehousePtr = &RemoteWarehouse{
		SgID:            req.WebToken.SgId,
		ID:              brandWarehousePtr.RemotePk,
		Name:            req.Name,
		StoreID:         req.StoreId,
		Address:         req.Address,
		PickUp:          req.PickUp,
		CityExpress:     req.CityExpress,
		OrdinaryExpress: req.OrdinaryExpress,
		Latitude:        req.Latitude,
		Longitude:       req.Longitude,
		CityCode:        req.CityCode,
		RegionCode:      req.RegionCode,
		Contact:         req.Contact,
		Mobile:          req.Mobile,
		Deadline:        req.Deadline,
		StorageMode:     req.StorageMode,
		PStoreID:        req.PStoreId,
		PermissionID:    uint32(brandWarehousePtr.PermissionID),
		Source:          req.Source,
	}
	return
}

// GetCommonExpressPlatforms 获取发货的快递平台
func (s *WarehouseServiceImp) GetCommonExpressPlatforms(ctx context.Context) (platforms []*model.ExpressPlatform, err error) {
	slog := util.NewContextLogger(ctx, s.slog)

	req := &inventory.GetCommonExpressPlatformsRequest{
		DeliverType: inventory.DeliverType_ORDINARY,
	}

	ctxWithTimout, cancel := s.proxy.NewContextWith(ctx)
	defer cancel()

	slog.Infof("GetCommonExpressPlatforms req:%v", req)

	reply, err := s.proxy.InventoryCl.GetCommonExpressPlatforms(ctxWithTimout, req)
	if err != nil {
		errReply := s.proxy.GetInventoryErrorInfo(err)
		if errReply != nil {
			err = fmt.Errorf("仓库侧获取物流平台错误:%s", errReply.ErrorMessage)
			slog.Infof("InventoryCl.GetCommonExpressPlatforms err====>type:%s,code:%s,msg:%s", errReply.ErrorType, errReply.ErrorCode, errReply.ErrorMessage)
		}
		return
	}

	for _, item := range reply.Platforms {
		platforms = append(platforms, &model.ExpressPlatform{
			ID:   item.Id,
			Name: item.Name,
		})
	}

	slog.Infof("1 获取快递平台 ok")

	return
}

func (s *WarehouseServiceImp) setInventoryWarehouseExpress(ctx context.Context, brandWarehousePtr *sqlc.BrandWarehouse) (err error) {
	slog := util.NewContextLogger(ctx, s.slog)

	platforms, err := s.GetCommonExpressPlatforms(ctx)
	if err != nil {
		return err
	}
	platformIDs := util.ToUInt32Slice(util.Map(util.ToGenericSlice(platforms), func(v interface{}) interface{} {
		val := v.(*model.ExpressPlatform)
		return val.ID
	}))

	webTokenPtr, err := s.genInventoryToken(ctx, brandWarehousePtr.BrandID)
	if err != nil {
		return
	}

	req := &inventory.EditOrdinaryExpressRegionsRequest{
		WebToken:  webTokenPtr,
		PStoreId:  0,
		Id:        uint32(brandWarehousePtr.RemotePk),
		Platforms: platformIDs,
		Regions: []*inventory.OrdinaryExpressRegionView{
			{
				FirstWeightPrice:      0,
				AdditionalWeightPrice: 0,
				Cities:                []uint32{CityCodeHangZhou},
			},
		},
	}

	ctxWithTimout, cancel := s.proxy.NewContextWith(ctx)
	defer cancel()

	_, err = s.proxy.InventoryCl.EditWarehouseOrdinaryExpressRegions(ctxWithTimout, req)
	if err != nil {
		errReply := s.proxy.GetInventoryErrorInfo(err)
		if errReply != nil {
			err = fmt.Errorf("仓库侧更新仓库绑定物流平台错误:%s", errReply.ErrorMessage)
			slog.Infof("InventoryCl.EditWarehouseOrdinaryExpressRegions err====>type:%s,code:%s,msg:%s", errReply.ErrorType, errReply.ErrorCode, errReply.ErrorMessage)
		}
		return
	}

	return
}
